package cn.fscode.code.generator.service.impl;

import cn.fscode.code.generator.assembly.GenTableAssembly;
import cn.fscode.code.generator.model.po.*;
import cn.fscode.code.generator.model.req.GeneratorReq;
import cn.fscode.code.generator.model.resp.CodeFileResp;
import cn.fscode.code.generator.repository.*;
import cn.fscode.code.generator.service.GenerateHistoryService;
import cn.fscode.code.generator.service.GeneratorService;
import cn.fscode.code.generator.template.TmplColumnDefinition;
import cn.fscode.code.generator.template.TmplContent;
import cn.fscode.code.generator.template.TmplTableDefinition;
import cn.fscode.code.generator.template.custom_var.TmplStrUtils;
import cn.fscode.code.generator.utils.FormatUtil;
import cn.fscode.code.generator.utils.VelocityUtil;
import cn.fscode.commons.tool.core.StringUtils;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.VelocityContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {
    static ExecutorService executorService = Executors.newFixedThreadPool(2);
    private GenerateHistoryService generateHistoryService;
    private final DatasourceConfigRepository datasourceConfigRepository;
    private final GenTableRepository genTableRepository;
    private final GenTableColumnRepository genTableColumnRepository;
    private final GenTableAssembly genTableAssembly;
    private final TemplateConfigRepository templateConfigRepository;
    private final GenTableIndexRepository genTableIndexRepository;

    @Value("${gen.format-xml:false}")
    private String formatXml;

    // 格式化代码
    private String formatCode(String fileName, String content) {
        if (Objects.equals("true", formatXml) && fileName.endsWith(".xml")) {
            return FormatUtil.formatXml(content);
        }
        return content;
    }

    private TmplTableDefinition getTmplTableDefinition(GenTablePO genTablePO, DatasourceConfigPO datasourceConfigPO) {
        TmplTableDefinition tableDefinition = new TmplTableDefinition();
        tableDefinition.setTableName(genTablePO.getTableName());
        tableDefinition.setComment(genTablePO.getTableComment());
        List<TmplColumnDefinition> columnDefinitions = this.getColumnDefinitions(genTablePO);
        List<GenTableIndexPO> genTableIndexList = genTableIndexRepository.listByTableId(genTablePO.getId());

        // 构建真实的行定义
        tableDefinition.setJavaColumnDefinitions(columnDefinitions.stream()
                .map(genTableAssembly::toTmplJavaColumnDefinition).collect(Collectors.toList()));
        tableDefinition.setCsharpColumnDefinitions(columnDefinitions.stream()
                .map(genTableAssembly::toTmplCsharpColumnDefinition).collect(Collectors.toList()));
        tableDefinition.setTableIndexDefinition(genTableIndexList.stream()
                .map(genTableAssembly::toTmplTableIndexDefinition).collect(Collectors.toList()));
        return tableDefinition;
    }

    private List<TmplColumnDefinition> getColumnDefinitions(GenTablePO genTablePO) {
        List<GenTableColumnPO> list = genTableColumnRepository.listByTableId(genTablePO.getId());
        return list.stream().map(genTableAssembly::toTmplColumnDefinition)
                .collect(Collectors.toList());
    }

    /**
     * 返回SQL上下文列表
     *
     * @param genTablePO 生成表数据
     * @param datasourceConfigPO 配置
     * @return 返回SQL上下文
     */
    private List<TmplContent> buildTmplContextList(GenTablePO genTablePO, DatasourceConfigPO datasourceConfigPO,
                                                   GeneratorReq req) {
        Assert.notNull(genTablePO, "gen table not exist");
        List<TmplContent> contextList = new ArrayList<>();
        TmplTableDefinition tableDefinition = this.getTmplTableDefinition(genTablePO, datasourceConfigPO);
        TmplContent tmplContent = new TmplContent();
        tmplContent.setTableDefinition(tableDefinition);
        tmplContent.setDbName(datasourceConfigPO.getDbName());
        tmplContent.setAuthor(req.getAuthor());
        tmplContent.setBusinessName(req.getBusinessName());
        tmplContent.setFunctionName(req.getFunctionName());
        tmplContent.setModuleName(req.getModuleName());
        tmplContent.setPackageName(req.getPackageName());
        tmplContent.setDelPrefix(req.getDelPrefix());
        contextList.add(tmplContent);
        return contextList;
    }

    private void attributeSetter(String value, Consumer<String> setter) {
        if (StringUtils.hasText(value)) {
            setter.accept(value);
        }
    }

    private String doGenerator(TmplContent tmplContent, String template) {
        if (template == null) {
            return "";
        }
        VelocityContext context = new VelocityContext();
        Object pkColumn = tmplContent.getTableDefinition().getPkColumn();
        if (pkColumn == null) {
            pkColumn = Collections.emptyMap();
        }
        context.put("context", tmplContent);
        context.put("table", tmplContent.getTableDefinition());
        context.put("tableIndexs", tmplContent.getTableDefinition().getTableIndexDefinition());
        context.put("pk", pkColumn);
        context.put("javaColumns", tmplContent.getTableDefinition().getJavaColumnDefinitions());
        context.put("csharpColumns", tmplContent.getTableDefinition().getCsharpColumnDefinitions());
        context.put("strUtils", new TmplStrUtils());

        return VelocityUtil.generate(context, template);
    }

    @Override
    public List<CodeFileResp> generate(GeneratorReq req) {
        List<CodeFileResp> respList = new ArrayList<>();
        GenTablePO genTablePO = genTableRepository.getById(req.getGenTableId());
        Long datasourceId = genTablePO.getDatasourceId();
        DatasourceConfigPO datasourceConfigPO = datasourceConfigRepository.getById(datasourceId);
        List<TmplContent> tmplContents = this.buildTmplContextList(genTablePO, datasourceConfigPO, req);
        List<TemplateConfigPO> templateConfigPOS = templateConfigRepository
                .listByIds(StringUtils.toList(req.getTemplateIds(), ",", Long.class));
        for (TmplContent tmplContent : tmplContents) {
            for (TemplateConfigPO template : templateConfigPOS) {
                String content = formatContent(template.getContent());
                attributeSetter(req.getPackageName(), tmplContent::setPackageName);
                attributeSetter(req.getDelPrefix(), tmplContent::setDelPrefix);
                attributeSetter(req.getAuthor(), tmplContent::setAuthor);
                String folder = template.getFolder();
                if (StringUtils.isEmpty(folder)) {
                    folder = template.getName();
                } else {
                    //文件目录可以使用变量
                    folder = doGenerator(tmplContent, folder);
                }
                attributeSetter(folder, tmplContent::setPackageSubPath);

                //获取文件名
                String fileName = doGenerator(tmplContent, template.getFileName());
                String parseResultContent = doGenerator(tmplContent, content);
                parseResultContent = this.formatCode(fileName, parseResultContent);
                CodeFileResp codeFile = new CodeFileResp();
                codeFile.setFolder(folder);
                codeFile.setFileName(fileName);
                codeFile.setContent(parseResultContent);
                respList.add(codeFile);
            }
        }
//        executorService.execute(() -> generateHistoryService.saveHistory(req));
        return respList;
    }

    /**
     * 格式化模板内容, 我们如果将 #set, #if等模板引擎内置变量没有顶头写, 会导致解析出的内容
     * 缩进有问题, 所以这里将内置语法变量全部顶头, 包括以##开头的注释
     * @param content 内容
     */
    private String formatContent(String content) {
        List<String> lineList = new ArrayList<>(Arrays.asList(content.split("\n")));
        StringBuilder sb = new StringBuilder();
        for (String line : lineList) {
            String[] startsWith = new String[] {
                    "#set", "#if", "#else", "##", "#foreach", "#end", "#break", "#stop"
            };
            if (StringUtils.startsWithAny(StrUtil.trimStart(line), startsWith)) {
                sb.append(StrUtil.trimStart(line));
            } else {
                sb.append(line);
            }
            sb.append("\n");
        }
        return sb.toString();
    }
}
